/*
  Copyright © 2010  Vinícius dos Santos Oliveira
  Copyright © 2010  Felipe Carlos Lima dos Santos

      This file is part of Touché.

      Touché is free software: you can redistribute it and/or modify
      it under the terms of the GNU Lesser General Public License as published
      by the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.

      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Lesser General Public License for more details.

      You should have received a copy of the GNU Lesser General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */

#include "stageactor.h"
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include "stageactorlink.h"
#include "stage.h"
#include <QEvent>
#include <QDebug>
#include <QTimer>
#include "colaborativecontroller.h"
using namespace Touche;

//TODO: agora os elementos não são mais todos do mesmo tamanho, logo, os atributos abaixo deveriam ser removidos
static QSizeF m_elementSize(64., 64.);
static qreal m_elementCenter[2] = {32., 32.};
static QPointF m_elementHiddenPos(400., 0.);
static QPointF m_elementInitialPos(400., 240.);
static QPointF m_elementTrashPos(400., 480.);
static const QRectF sceneRect(-800., -480., 2400., 1440.);
static const QColor onLockedPallete[2] = {Qt::red, Qt::red};
static const QColor onEditing[2] = {Qt::green, Qt::green};

StageActor *StageActor::opened = NULL;
QGraphicsScene *StageActor::dialog = NULL;
QHash<QUuid, Element *> StageActor::m_elements;
QHash<Element *, StageActor *> StageActor::m_actors;

const QHash<QUuid, Element *> &StageActor::elements()
{
    return m_elements;
}

StageActor *StageActor::getActor(Element *element)
{
    return m_actors[element];
}

StageActor::StageActor(Element *element) :
        m_element(element),
        locked(false),
        m_item(new DraggableToggleButton),
        view(NULL),
        menuOptions(element->menuOptions()),
        menuButtons(menuOptions.size(), NULL)
{
    m_elements.insert(element->id, element);
    m_actors.insert(element, this);

    m_item->updateBoundingRect(m_element->item(m_item));
    m_item->setData(0, QVariant::fromValue(static_cast<QObject *>(this)));
    m_item->setFlag(QGraphicsItem::ItemIsMovable);

    connect(m_element, SIGNAL(menuUpdated(QVector<QString>)), this, SLOT(onMenuUpdated(QVector<QString>)));
    connect(m_element, SIGNAL(lockElement(bool)), this, SLOT(onElementLocked(bool)));
    connect(m_element, SIGNAL(contentsChanged()), this, SLOT(onContentsChanged()));
    connect(m_element, SIGNAL(sceneRequest()), this, SLOT(onSceneRequest()));
    connect(m_element, SIGNAL(stageRequest()), this, SLOT(onStageRequest()));
    connect(m_element, SIGNAL(itemBoundingRectChanged(QRectF)), m_item, SLOT(updateBoundingRect(QRectF)));

    connect(m_item, SIGNAL(dragged(DraggableToggleButton*)), this, SLOT(onDragged(DraggableToggleButton*)));
    connect(m_item, SIGNAL(toggled(bool)), this, SLOT(onButtonToggled(bool)));
    connect(m_item, SIGNAL(doubleClicked()), this, SLOT(onDoubleClicked()));
}

StageActor::~StageActor()
{
    m_elements.remove(m_element->id);
    m_actors.remove(m_element);

    closeMenu();
    m_item->setData(0, QVariant::fromValue(static_cast<QObject *>(0)));

    {
        StageActorLink::mutex.lock();
        for(QList<StageActorLink *>::iterator i = links.begin();i != links.end();++i){
            StageActor *sink = (*i)->sink(this);
            for(QList<StageActorLink *>::iterator j = sink->links.begin();j != sink->links.end();++j){
                if(*i == *j){
                    sink->links.erase(j);
                    break;
                }
            }
            StageActorLink::eraseFromList(*i);
        }
        StageActorLink::mutex.unlock();
    }

    QPropertyAnimation *animation = new QPropertyAnimation(m_item, "pos");
    animation->setEndValue(m_item->parentItem()->mapFromScene(m_elementTrashPos) - QPointF(m_elementCenter[0], 0.));
    animation->setDuration(500);
    animation->setEasingCurve(QEasingCurve::InOutExpo);
    connect(animation, SIGNAL(destroyed()), m_item, SLOT(deleteLater()));
    connect(animation, SIGNAL(destroyed()), m_element, SLOT(deleteLater()));
    animation->start(QAbstractAnimation::DeleteWhenStopped);
}

void StageActor::onButtonToggled(bool enabled)
{
    if(enabled){
        if(opened){
            opened->closeMenu();
        }
        //para ter certeza da sincronização, acho que nem estar sendo usado e precisa ser eliminado
        usleep(10000);
        this->openMenu();
    }else{
        this->closeMenu();
    }
}

void StageActor::onDoubleClicked()
{
//    if(opened){
//        opened->closeMenu();
//    }
//    if(m_element->isEditable()){
//        editMode();
//    }else{
//        viewMode();
//    }
}

void StageActor::openMenu()
{
    if(opened){
        opened->closeMenu();
    }
    QEasingCurve curve(QEasingCurve::OutBack);
    QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup;
    opened = this;
    m_item->setChecked(true);

    {
        qreal rotation = 0.;

        for(int i = 0;i < menuOptions.size();++i){
            PushTextButtonItem *&menuItem = menuButtons[i];
            menuItem = new PushTextButtonItem(menuOptions.at(i), ":/gfx/svg/item_option.svg", m_item);
            menuItem->setPos(60., -100.);
            menuItem->setTransformOriginPoint(0., 160.);

            menuItem->setData(0, QVariant::fromValue(menuOptions.at(i)));
//            qDebug() << menuItem->data(0).toString();
            connect(menuItem, SIGNAL(clicked(PushButtonItem*)), this, SLOT(onMenuActived(PushButtonItem*)));

            QPropertyAnimation *animation = new QPropertyAnimation(menuItem, "rotation");
            animation->setEndValue(static_cast<qreal>(rotation));
            animation->setEasingCurve(curve);
            animation->setDuration(300);
            animationGroup->addAnimation(animation);

            rotation += 22.;
        }
    }



    animationGroup->start(QAbstractAnimation::DeleteWhenStopped);
}

void StageActor::closeMenu()
{
    if(opened == this){
        opened = NULL;
        m_item->setChecked(false);

        QEasingCurve curve(QEasingCurve::InCubic);
        QPropertyAnimation *animation = NULL;

        for(int i = 0;i < menuButtons.size();++i){
            animation = new QPropertyAnimation(menuButtons.at(i), "rotation");
            animation->setEasingCurve(curve);
            animation->setEndValue(0.);
            animation->setDuration(200);
            connect(animation, SIGNAL(destroyed()), menuButtons.at(i), SLOT(deleteLater()));
            animation->start(QAbstractAnimation::DeleteWhenStopped);
        }
    }
}

void StageActor::onMenuActived(PushButtonItem *button)
{
    disconnect(sender(), SIGNAL(clicked(PushButtonItem*)), this, SLOT(onMenuActived(PushButtonItem*)));
    QString option(button->data(0).toString());
    this->closeMenu();
    m_element->onMenuActived(option);
}

void StageActor::onMenuUpdated(const QVector<QString> &newMenu)
{
    if(opened = this){
        closeMenu();
    }
    menuOptions = newMenu;
    menuButtons.resize(newMenu.size());
}

void StageActor::onElementLocked(bool locked)
{
    if(this->locked != locked){
        this->locked = locked;

        if(sender() == m_element){
            // travar elemento dos outros
            if(locked){
                ColaborativeController::getInstance()->lockElement(m_element);
                m_item->setColor(onEditing[0], onEditing[1]);
            }else{
                ColaborativeController::getInstance()->unlockElement(m_element);
            }
        }else{
            // elemento foi travado
            m_element->onElementLocked(locked);

            if(locked)
                m_item->setColor(onLockedPallete[0], onLockedPallete[1]);
        }
        if(!locked)
            m_item->setColor();
    }
}

void StageActor::onContentsChanged()
{
    ColaborativeController::getInstance()->colaborateElement(m_element);
}

void StageActor::onSceneRequest()
{
    if(!dialog){
        if(!view){
            if(m_item->scene()->views().isEmpty()){
                return; // TODO: encerrar a aplicação
            }
            view = m_item->scene()->views().first();
        }
        view->setScene(dialog = new QGraphicsScene(0., 0., 800., 480.));
        m_element->onSceneReceived(dialog);
    }
}

void StageActor::onStageRequest()
{
    view->setScene(m_item->scene());
    dialog->deleteLater();
    dialog = NULL;
}

//void StageActor::editMode()
//{
//    connect(m_element, SIGNAL(edition_finished(bool)), this, SLOT(onEditionFinished(bool)));
//    closeMenu();
//    if(m_element->flags().testFlag(Element::EDITABLE_IN_STAGE)){
//        m_element->edit();
//    }else if(m_element->flags().testFlag(Element::EDITABLE_OUT_STAGE)){
//        if(!m_item->scene()->views().isEmpty()){
//        }else{
//            return;
//        }
//        dialog = new QGraphicsScene(0., 0., 800., 480.);
//        view->setScene(dialog);
//        outView = true;
//        m_element->edit(dialog);
//    }
//}

void StageActor::onDragged(DraggableToggleButton *button)
{
    StageActor *sink = reinterpret_cast<StageActor *>(button->data(0).value<QObject *>());

    if(!StageActorLink::exists(this, sink)){
        StageActorLink *link = new StageActorLink(this, sink);
        links.push_back(link);
        sink->links.push_back(link);
    }
}

//void StageActor::onEditionFinished(bool updateItem)
//{
//    disconnect(m_element, SIGNAL(edition_finished(bool)), this, SLOT(onEditionFinished(bool)));
//    if(updateItem){
//        m_item->updateBoundingRect(m_element->item(m_item));
//    }
//
//    // element is in your own scene
//    if(outView){
//        view->setScene(m_item->scene());
//        dialog->deleteLater();
//        dialog = NULL;
//        outView = false;
//    }
//}

void StageActor::calculateForces()
{
    /*
    ** Se o item estiver sendo arrastado, consideramos que as forças aplicadas
    ** sobre ele são zero.
    */
    if (!m_item->scene() || m_item->scene()->mouseGrabberItem() == m_item) {
        newPos = m_item->pos();
        return;
    }

    // Sum up all forces pushing this item away
    qreal xvel = 0;
    qreal yvel = 0;

    for(QListIterator<DraggableToggleButton *> i(DraggableToggleButton::buttons());i.hasNext();){
        QLineF line(m_item->mapFromItem(i.next(), 0, 0), QPointF(0, 0));
        qreal dx = line.dx();
        qreal dy = line.dy();
        double l = 0.5 * (dx * dx + dy * dy);
        if (l > 0) {
            xvel += (dx * 150.0) / l;
            yvel += (dy * 150.0) / l;
        }
    }

    // Now subtract all forces pulling items together
    qreal radius = m_item->boundingRect().width() / 2;
    qreal weight = (links.size() + 1) * radius;
    foreach (StageActorLink *edge, links) {
        QPointF pos;
        if (edge->actors()[0] == this)
            pos = m_item->mapFromItem(edge->actors()[1]->m_item, 0, 0);
        else
            pos = m_item->mapFromItem(edge->actors()[0]->m_item, 0, 0);
        xvel += pos.x() / weight;
        yvel += pos.y() / weight;
    }

    if (qAbs(xvel) < 0.1 && qAbs(yvel) < 0.1)
        xvel = yvel = 0;

    newPos = m_item->pos() + QPointF(xvel, yvel);
    newPos.setX(qMin(qMax(newPos.x(), sceneRect.left()), sceneRect.right() - 2 * radius));
    newPos.setY(qMin(qMax(newPos.y(), sceneRect.top()), sceneRect.bottom() - 2 * radius));
}

bool StageActor::advance()
{
    if (newPos == m_item->pos())
        return false;

    m_item->setPos(newPos);
    return true;
}
